package nsga;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import common.GeneticHelper;
import common.GeneticOperations;
import common.NonDominatedAgentsAggregator;


public class Nsga2Algorithm <Genotype> {
    
	private GeneticOperations<Genotype> go;
	private List<Individual<Genotype>> population; // population
	private List<List<Individual<Genotype>>> fronts;
	private Nsga2Parameters params;
    private NonDominatedAgentsAggregator<Genotype,Individual<Genotype>> aggr;
    
	
	// generated
	private long durationOfSimulation = -1;
    private StringBuffer HvInTime = new StringBuffer();
    
	public static class Nsga2Parameters {
	    public int populationSize;
	    public int generationNumber;
	    public double parentsMutationProbability;
	    public double childrenMutationProbability;
	    public double ref_f0;
	    public double ref_f1;
	    public int timeLimit;
        public String inputFile;
        public int hvCalcInterval;
	}

	public Nsga2Algorithm(GeneticOperations<Genotype> go, Nsga2Parameters parameters) {
	    
	    this.go = go;
	    this.params = parameters;
	    
		population = new ArrayList<Individual<Genotype>>(params.populationSize);
		for (int i = 0; i < params.populationSize; i++) {
			population.add(new Individual<Genotype>(this.go.createRandomGenotype(),go));
		}
		
		aggr = new NonDominatedAgentsAggregator<Genotype,Individual<Genotype>>();
	}
		
	public void runExperiment() {
	    
        long beginMili = System.currentTimeMillis();
       
		Random rand = new Random();
		int gNum;
		
		it_loop:
		for (gNum = 0; params.generationNumber == 0 || gNum < params.generationNumber; gNum++) {
			
		    ArrayList<Individual<Genotype>> Pcopy = new ArrayList<Individual<Genotype>>(this.population);
			// create child population
			List<Individual<Genotype>> childenPopulation = new ArrayList<Individual<Genotype>>(params.populationSize);
   
			while (Pcopy.size() >= 2) {
			    
				Individual<Genotype> p1 = Pcopy.remove(rand.nextInt(Pcopy.size()));
				Individual<Genotype> p2 = Pcopy.remove(rand.nextInt(Pcopy.size()));

				childenPopulation.add(p1.crossover(p2).get(0));
				childenPopulation.add(p2.crossover(p1).get(0));
			}
			
			childenPopulation = mutatedPopulation(childenPopulation, params.childrenMutationProbability);
			
			List<Individual<Genotype>> parentAndChildren = mutatedPopulation(population, params.parentsMutationProbability); 
			parentAndChildren.addAll(childenPopulation);
			
			this.fronts = Nsga2Algorithm.fastNondominatedSort(parentAndChildren);
			
			population = new ArrayList<Individual<Genotype>>();
			int i = 0;
			while (population.size() < params.populationSize) {
				population.addAll(this.fronts.get(i));
				i += 1;		
			}
			
			crowdingDistanceAssignment(population);
			Collections.sort(population, new Comparator<Individual<Genotype>>() {
				public int compare(Individual<Genotype> i, Individual<Genotype> j) {
					if (i.rank < j.rank || (i.rank == j.rank && i.distance > j.distance)) {
						return -1;
					}
					return 1;
				}	
			});
			population = population.subList(0, params.populationSize);
			
	         if (gNum % params.hvCalcInterval == 0) {
	             long time = System.currentTimeMillis() - beginMili;
	             
	             aggr.addAllAgents(fronts.get(0));
	             
	             double HV = GeneticHelper.calculateHiperValume(fronts.get(0),params.ref_f0,params.ref_f1);
	             int HV_size = fronts.get(0).size();
	             
	             double HV_aggr = GeneticHelper.calculateHiperValume(aggr.getAgents(),params.ref_f0,params.ref_f1);
	             int HV_aggr_size = aggr.getAgents().size();
	             
	             System.out.println("czas "+time+" generacja "+gNum+" HV ("+HV_size+") "+HV+" HV_all ("+HV_aggr_size+") "+HV_aggr);
	             HvInTime.append(""+time+", "+HV_aggr+"\n");
	             
	             if (time > params.timeLimit) {
	                 System.out.println("Czas się skończył, ilość generacji = "+gNum);
	                 break it_loop;
	             }
	         }
		}
		if (gNum == params.generationNumber) {
		    System.out.println("Wszystkie iteracje skończone");
		}
        long endMili = System.currentTimeMillis();
        durationOfSimulation = (endMili - beginMili) / 1000;
	}
	
	public List<List<Individual<Genotype>>> getFronts() {
	    return this.fronts;
	}

    public List<Individual<Genotype>> getPopulation() {
        return this.population;
    }
    
    public String getHvInTime() {
        return HvInTime.toString();
    }
    
    public static <Genotype> void crowdingDistanceAssignment(List<Individual<Genotype>> I) { 

        int last = I.size()-1;
        int numOfValues = I.get(0).getFitnesses().size();
        for (int objNum = 0; objNum < numOfValues; objNum++) {
            // sorting using i'th objective values
            final int objNumF = objNum;
            Collections.sort(I, new Comparator<Individual<Genotype> >() {
                public int compare(Individual<Genotype>  o1, Individual<Genotype>  o2) {
                    return new Double(o1.getFitness(objNumF)).compareTo(o2.getFitness(objNumF));
                }
            });
            I.get(0).distance = Double.MAX_VALUE;
            I.get(last).distance = Double.MAX_VALUE;
            for (int i = 1; i < last; i++) {
                I.get(i).distance += I.get(i+1).getFitness(objNum) - I.get(i-1).getFitness(objNum);
            }
        }
        
    }
    public static <Genotype> List<List<Individual<Genotype>>> fastNondominatedSort(List<Individual<Genotype>> P) {  
        
        for (Individual<Genotype> p: P) {
            p.dominatedByNbr = 0;
            p.dominateSet = new LinkedList<Individual<Genotype>>();
            p.rank = null;
        }
        List<Individual<Genotype>> front0 = new LinkedList<Individual<Genotype>>();
        for (Individual<Genotype> p: P) {
            for (Individual<Genotype> q: P) {
                if (p == q)
                    continue;
                
                int domination = p.checkDomination(q);
                if (domination == 1) {
                    p.dominateSet.add(q);
                }
                else if (domination == -1){
                    p.dominatedByNbr += 1;                  
                }
            }
            if (p.dominatedByNbr == 0) {
                p.rank = 0;
                front0.add(p);
            }
        }
        List<List<Individual<Genotype>>> fronts = new LinkedList<List<Individual<Genotype>>>();
        fronts.add(front0);
        
        int i = 0;
        while (fronts.get(i).size() > 0) {
            List<Individual<Genotype>> H = new LinkedList<Individual<Genotype>>();
            for (Individual<Genotype> p : fronts.get(i)) {
                for (Individual<Genotype> q: p.dominateSet) {
                    q.dominatedByNbr -= 1;
                    if (q.dominatedByNbr == 0) {
                        q.rank = i + 1;
                        H.add(q);
                    }
                }
            }
            i += 1;
            fronts.add(H);
        }
        return fronts;
    }
    
    public void printFrontsNum() {
        
        System.out.println("Ilość frontów = "+fronts.size());
        for (int i = 0; i < fronts.size(); i++) {
            System.out.println("\tFRONT "+i+" liczebność = "+fronts.get(i).size());
        }
        System.out.println();
    }
    
    public List<Individual<Genotype>> getAllNonDominated() {
        
        return aggr.getAgents();
    }
    
    public void printExperimentDetails() {
        
        System.out.println("populactionSize             = " + params.populationSize);
        System.out.println("generationNumber            = " + params.generationNumber);
        System.out.println("parentsMutationProbability  = " + params.parentsMutationProbability);
        System.out.println("childrenMutationProbability = " + params.childrenMutationProbability);
        System.out.println("HV_ref0                     = " + params.ref_f0);
        System.out.println("HV_ref1                     = " + params.ref_f1);
    }
    
    public void printExperimentSummary() {
        
        System.out.println("czas trwania     = " + durationOfSimulation);
    }
    
    public static <Genotype> List<Individual<Genotype>> mutatedPopulation(List<Individual<Genotype>> population, double mutationProb) {
        
        List<Individual<Genotype>> result = new ArrayList<Individual<Genotype>>(population.size());
        
        for (Individual<Genotype> ind : population) {
            if (Math.random() < mutationProb) {
                result.add(ind.mutated());
            } else {
                result.add(ind);
            }
        }
        return result;
        
    }
}